package top.healthylife.gateway.platform.geo.api.util.client;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import top.healthylife.basics.utils.exception.BusinessException;
import top.healthylife.gateway.platform.geo.api.config.SystemConfig;
import top.heathylife.gateway.common.po.RecordRequestInfo;

import java.security.KeyPair;
import java.util.*;

/**
 * 数据请求client
 * wushujia
 */
@Slf4j
@SuppressWarnings("all")
@Data
public class Client {
    private String server = "http://yz.geotmt.com"; // http://yz.geotmt.com、https://yz.geotmt.com
    private int encrypted = 1; // 是否加密传输
    private String encryptionType = "AES"; // AES(秘钥长度不固定)、DES(秘钥长度8)、DESede(秘钥长度24)
    private String etype = "RSA";
    private int dsign = 0; // 是否进行数字签名 1是0否
    //暂时不用
    private String spiderEncryptionType = " "; // AES(秘钥长度不固定)、DES(秘钥长度8)、DESede(秘钥长度24)
    //暂时不用
    private String spiderEncryptionKey = " "; // 加密类型和加密秘钥向GEO索取(如果是获取数据的时候传的是RSA那么这里自己定义即可)
    private String encryptionKey; // 加密类型和加密秘钥向GEO索取(如果是获取数据的时候传的是RSA那么这里自己定义即可)
    private static final String SUCCESS = "200";
    private String username; // 账户向GEO申请开通
    private String password;
    private String uno;
    private static final int httpConnectTimeout = 10000;
    private static final int httpReadTimeout = 10000;
    private Map<String, String> tokenIdMap = Token.getInstance().getTokenIdMap();
    private Map<String, Long> getTokenTimeMap = Token.getInstance().getGetTokenTimeMap();
    private Map<String, String> digitalSignatureKeyMap = Token.getInstance().getDigitalSignatureKeyMap();

    private static final long tokenCycle = 86400000L;
    // 避免高并发时刚好token过期造成同时多个线程一起申请新token，如果在此时间内有过更新那么直接返回内存里面的token
    private static final long tokenCyc = 35000L;
    /**
     * 接口请求返回的code码
     */
    private static Map<String, String> apiCodeMap = new HashMap<>(64);

    public Client(SystemConfig systemConfig) {
        log.info("加密key{}", systemConfig.getEncryptionKey());
        log.info("加密类型{}", encryptionType);
        this.setEncryptionKey(systemConfig.getEncryptionKey());
        this.setUsername(systemConfig.getUsername());
        this.setPassword(systemConfig.getPassword());
        this.setUno(systemConfig.getUno());
        this.setTokenIdMap(Token.getInstance().getTokenIdMap());
        this.setGetTokenTimeMap(Token.getInstance().getGetTokenTimeMap());
        this.setDigitalSignatureKeyMap(Token.getInstance().getDigitalSignatureKeyMap());
    }


    static {
        apiCodeMap.put("200", "成功");
        apiCodeMap.put("-888", "登录失败,用户不存在");
        apiCodeMap.put("-999", "用户名不能为空");
        apiCodeMap.put("-747", "该用户已经删除");
        apiCodeMap.put("-700", "登陆出现异常");
        apiCodeMap.put("-666", "登陆失败,记录登陆信息失败");
        apiCodeMap.put("-555", "登录失败,用户名密码错误");
        apiCodeMap.put("-556", "图形验证码输入错误");
        apiCodeMap.put("-444", "登录失败,账户被锁定");
        apiCodeMap.put("-100", "token为空（需要重新获tokenID）");
        apiCodeMap.put("-200", "用户被新登录挤掉（需要重新获tokenID）");
        apiCodeMap.put("-300", "用户未登录（需要重新获tokenID）");
        apiCodeMap.put("-400", "用户未具备访问权限");
        apiCodeMap.put("500", "服务器错误");
        apiCodeMap.put("-41054", "注册失败");
        apiCodeMap.put("50001", "剩余使用量不足");
        apiCodeMap.put("50002", "接口查询接口类型参数配置错误");
        apiCodeMap.put("50003", "文件拷贝错误");
        apiCodeMap.put("50004", "业务类型为空");
        apiCodeMap.put("1100000", "授权码格式错误");
        apiCodeMap.put("1000000", "暂不支持此运营商");
        apiCodeMap.put("1000005", "type为空或type格式错误(正确示例：A1,A5,B1)");
        apiCodeMap.put("1000006", "手机号码错误(没有匹配的运营商)");
        apiCodeMap.put("1000020", "手机号不能为空");
        apiCodeMap.put("1000009", "手机号格式错误");
        apiCodeMap.put("1000019", "手机号2格式错误");
        apiCodeMap.put("1000021", "身份证号不能为空");
        apiCodeMap.put("1000007", "身份证号格式错误");
        apiCodeMap.put("1000008", "姓名不能为空");
        apiCodeMap.put("49999", "余额不足");
        apiCodeMap.put("100000801", "姓名不能为乱码");
        apiCodeMap.put("100000802", "姓名格式错误");
        apiCodeMap.put("501", "参数错误");
        apiCodeMap.put("10009", "系统异常");
        apiCodeMap.put("10010", "对象异常");
        apiCodeMap.put("10011", "其他异常");
        apiCodeMap.put("10012", "签名验证失败");
        apiCodeMap.put("10013", "数据查询超时");
        apiCodeMap.put("100000001", "不支持虚拟运营商");
        apiCodeMap.put("49998", "T、Y、Z、E系列接口不能和其他接口同时调用");
        apiCodeMap.put("50020", "回调接口和实时接口不能同时调用");
        apiCodeMap.put("1000074", "超出并发数");
        apiCodeMap.put("1000075", "超出当日最大调用量");
    }

    public <T> T getApiData(String url, Map<String, Object> paramsMap, SystemConfig geoAccount, Class<T> cls) throws IllegalAccessException, InstantiationException {
        String path = this.server + "/civp/getview/api/u/queryUnify";
        String authCode = geoAccount.getAuthCode();
        paramsMap.put("authCode", authCode);
        log.info("入参:{}", JSONObject.toJSONString(paramsMap));
        String decryptRs = getData(path, paramsMap, "civp");
        log.info("解密后的数据:{}", decryptRs);
        if (ObjectUtils.isNotEmpty(decryptRs)) {
            JSONObject jsonObject = JSONObject.parseObject(decryptRs);
            String code = jsonObject.get("code").toString();
            String msg = jsonObject.get("msg") != null ? jsonObject.get("msg").toString() : "";
            if (SUCCESS.equals(code)) {
                JSONObject data1 = jsonObject.getJSONObject("data");
                data1.put("authCode", authCode);
                return data1.toJavaObject(cls);
            } else {
                String desc = apiCodeMap.get(code);
                throw new BusinessException(desc == null ? StrUtil.format("三方接口返回异常:{}", msg) : desc);
            }
        }
        return cls.newInstance();
    }

    public String getData(String path, Map<String, Object> params, String sys) {
        return getData(path, params, sys, httpConnectTimeout, httpReadTimeout);
    }

    /**
     * @param params
     * @param sys
     * @return
     */
    public Map<String, Object> encryptAndDigitalSignature(Map<String, String> params, String sys) {
        Map<String, String> headers = new HashMap<String, String>();
        TreeMap<String, String> paraMap = new TreeMap<String, String>();
        String tokenId = getToken(true);
        StringBuilder sb = new StringBuilder();
        if (params != null) {
            Set<String> set = params.keySet();
            for (String k : set) {
                String value = params.get(k);
                if (dsign == 1) {
                    paraMap.put(k, value);
                }
                if (encrypted == 1) {
                    // value = Secret.encrypt(encryptionType,value, encryptionKey)  ; // 加密
                    value = encrypt(value, sys); // 加密
                }
                sb.append(k).append("=").append(value).append("&");
            }
        }
        String sbStr = sb.toString();
        if (sb != null && sb.length() > 0) {
            sbStr = sb.substring(0, sb.length() - 1);
        }
        if (dsign == 1) {
            paraMap.put("tokenId", tokenId);
            String digitalSignature = DigitalSignature.clientDigitalSignature(paraMap, null, "", digitalSignatureKeyMap.get(username));
            headers.put("digitalSignature", digitalSignature);
        }
        String newParams = sbStr + "&tokenId=" + tokenId;
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("para", newParams);
        result.put("headers", headers);
        return result;
    }

    /**
     * 该方法可以通过用户名密码直接调用数据接口，且token过期会自动重新获取
     *
     * @param path
     * @param params
     * @param sys
     * @param httpConnectTimeout 毫秒
     * @param httpReadTimeout    毫秒
     * @return
     */
    public String getData(String path, Map<String, Object> params, String sys, int httpConnectTimeout, int httpReadTimeout) {
        Map<String, String> headers = new HashMap<String, String>();
        TreeMap<String, String> paraMap = new TreeMap<String, String>();
        String tokenId = getToken(true);  // 等计费功能上线后第三个参数才能填RSA不然都只能是空
        StringBuilder sb = new StringBuilder();
        if (params != null) {
            Set<String> set = params.keySet();
            for (String k : set) {
                Object o = Optional.ofNullable(params.get(k)).orElseThrow(() -> new BusinessException("参数{}不允许为空", k));
                String value = o.toString();
                if (dsign == 1) {
                    paraMap.put(k, value);
                }
                if (encrypted == 1) {
                    // value = Secret.encrypt(encryptionType,value, encryptionKey)  ; // 加密
                    value = encrypt(value, sys); // 加密
                }
                sb.append(k).append("=").append(value).append("&");
            }
        }
        String sbStr = sb.toString();
        if (sb != null && sb.length() > 0) {
            sbStr = sb.substring(0, sb.length() - 1);
        }
        if (dsign == 1) {
            paraMap.put("tokenId", tokenId);
            String digitalSignature = DigitalSignature.clientDigitalSignature(paraMap, null, "", digitalSignatureKeyMap.get(username));
            headers.put("digitalSignature", digitalSignature);
        }
        String newParams = sbStr + "&tokenId=" + tokenId;
        String data = getDataByTokenId(path, newParams, headers, sys, httpConnectTimeout, httpReadTimeout);
        if (data != null && !"".equals(data)) {
            Map<String, Object> map = JSON.parseObject(data, Map.class);
            String code = map.get("code") + "";
            if ("-100".equals(code) || "-200".equals(code) || "-300".equals(code) || "-301".equals(code)) {
                System.out.println("tokenId无效重新获取tokenId");
                tokenId = getToken(false);  // 等计费功能上线后第三个参数才能填RSA不然都只能是空
                if (dsign == 1) {
                    paraMap.put("tokenId", tokenId);
                    String digitalSignature = DigitalSignature.clientDigitalSignature(paraMap, null, "", digitalSignatureKeyMap.get(username));
                    headers.put("digitalSignature", digitalSignature);
                }
                newParams = sbStr + "&tokenId=" + tokenId;
                data = getDataByTokenId(path, newParams, headers, sys, httpConnectTimeout, httpReadTimeout);
            }
        }
        return data;
    }

    /**
     * @param first
     * @return
     */
    public String getToken(boolean first) {
        if (first) {
            if (getNewToken()) {
                // 走网络获取token需要同步
                synchronized (username.intern()) {
                    return getToken();
                }
            } else {
                return tokenIdMap.get(username);
            }
        } else {
            synchronized (username.intern()) {
                Long tokenTime = getTokenTimeMap.get(username);
                long getTokenTime = tokenTime == null ? 0L : tokenTime;
                if (System.currentTimeMillis() - getTokenTime >= tokenCyc) {
                    // 避免高并发
                    tokenIdMap.put(username, ""); // 使token失效
                }
                String tokenId = getToken();
                return tokenId;
            }
        }
    }

    /**
     * 获取数据访问权限,该接口24小时请求一次即可！
     *
     * @return token
     */
    public String getToken() {
        if (getNewToken()) {
            String path = server + "/civp/getview/api/o/login";
            // 加密用户名密码
            String eUsername = username;
            String ePassword = password;
            String eDsign = dsign + "";
            if (encrypted == 1) {
                eUsername = encrypt(username);
                ePassword = encrypt(password);
                eDsign = encrypt(dsign + "");
            }
            String params = "";
            if ("RSA".equalsIgnoreCase(etype)) {
                KeyPair keyPair = RSAUtils.getKeyPair();
                String paraEncryptionType = RSAUtils.encrypt(keyPair.getPublic(), encryptionType);
                String paraEncryptionKey = RSAUtils.encrypt(keyPair.getPublic(), encryptionKey);
                params = "username=" + eUsername + "&password=" + ePassword + "&etype=" + etype + "&encryptionType=" + paraEncryptionType + "&encryptionKey=" + paraEncryptionKey + "&encrypted=" + encrypted + "&dsign=" + eDsign;
            } else {
                params = "username=" + eUsername + "&password=" + ePassword + "&uno=" + uno + "&encrypted=" + encrypted + "&dsign=" + eDsign;
            }
            String reqencoding = "UTF-8";
            String respencoding = "UTF-8";
            String requestMethod = "POST";
            Map<String, String> headerMap = null;
            log.info("参数>>>>:{}" + params);
            String rs = HttpClient.getRs(path, params, reqencoding, respencoding, requestMethod, httpConnectTimeout, httpReadTimeout, headerMap);
            log.info("结果>>>>:{}" + rs);
            if (rs != null && !"".equals(rs)) {
                // 解密返回值
                if (rs.startsWith("{")) {
                    log.info("未加密:{}" + rs);
                } else {
                    // rs = Secret.decrypt(encryptionType,rs, encryptionKey) ;
                    rs = decrypt(rs);
                    log.info("解密;{}" + rs);
                }
                Map<String, Object> map = JSON.parseObject(rs, Map.class);
                if ("200".equals(map.get("code") + "")) {
                    long getTokenTime = System.currentTimeMillis();
                    String tokenId = map.get("tokenId") + "";
                    tokenIdMap.put(username, tokenId);
                    getTokenTimeMap.put(username, getTokenTime);
                    Map data = (Map) map.get("data");
                    if (data != null) {
                        String digitalSignatureKey = (String) data.get("digitalSignatureKey");
                        if (digitalSignatureKey != null) {
                            digitalSignatureKeyMap.put(username, digitalSignatureKey);
                        }
                    }
                    return tokenId;
                } else {
                    log.info("登录失败!code={}" + map.get("code"));
                    throw new BusinessException("登录集奥失败,{}", map.get("code"));
                }
            } else {
                log.info("登录获取token失败,{}", params);
                throw new BusinessException("登录获取token失败");
            }
        } else {
            return tokenIdMap.get(username);
        }
    }

    /**
     * 获取数据
     *
     * @param path
     * @param params
     * @param headers
     * @param sys
     * @param httpConnectTimeout
     * @param httpReadTimeout
     * @return
     */
    public String getDataByTokenId(String path, String params, Map<String, String> headers, String sys, int httpConnectTimeout, int httpReadTimeout) {
        String reqencoding = "UTF-8";
        String respencoding = "UTF-8";
        String requestMethod = "POST";
        log.info("验证四要素参数{}", params);
        //记录请求信息
        RecordRequestInfo.recordRequest(path, JSONUtil.toJsonStr(params), headers, "post");
        String rs = HttpClient.getRs(path, params, reqencoding, respencoding, requestMethod, httpConnectTimeout, httpReadTimeout, headers);
        log.info("验证四要素结果{}", rs);
        if (rs != null && !"".equals(rs)) {
            // 解密返回值
            if (rs.startsWith("{")) {
                System.out.println("未加密:" + rs);
            } else {
                rs = decrypt(rs, sys);
                System.out.println("解密:" + rs);
            }
            Map<String, Object> map = JSON.parseObject(rs, Map.class);
			/*
			if(!"200".equals(map.get("code")+"")){
				System.out.println("数据请求失败!code="+map.get("code"));
			}
			*/
        }
        return rs;
    }

    /**
     * 登出接口
     *
     * @return
     */
    public String loginOut(String sys) {
        String tokenId = tokenIdMap.get(username);
        if (tokenId != null && !"".equals(tokenId)) {
            String digitalSignatureKey = digitalSignatureKeyMap.get(username);
            Map<String, String> headers = new HashMap<String, String>();
            if (digitalSignatureKey != null && !"".equals(digitalSignatureKey)) {
                TreeMap<String, String> paraMap = new TreeMap<String, String>();
                paraMap.put("tokenId", tokenId);
                String digitalSignature = DigitalSignature.clientDigitalSignature(paraMap, null, "", digitalSignatureKeyMap.get(username));
                headers.put("digitalSignature", digitalSignature);
            }
            return loginOut(tokenId, headers, sys);
        }
        System.out.println("用户未登录");
        return "";
    }

    /**
     * 登出(该token权限将被回收),该接口一般不需要调用
     *
     * @param tokenId
     * @return
     */
    public String loginOut(String tokenId, Map<String, String> headerMap, String sys) {
        String path = server + "/civp/getview/api/u/logout";
        String params = "tokenId=" + tokenId;
        String reqencoding = "UTF-8";
        String respencoding = "UTF-8";
        String requestMethod = "POST";
        String rs = HttpClient.getRs(path, params, reqencoding, respencoding, requestMethod, httpConnectTimeout, httpReadTimeout, headerMap);
        if (rs != null && !"".equals(rs)) {
            // 解密返回值
            if (rs.startsWith("{")) {
                System.out.println("未加密:" + rs);
            } else {
                rs = decrypt(rs, sys);
                System.out.println("解密:" + rs);
            }
            Map<String, Object> map = JSON.parseObject(rs, Map.class);
            if ("200".equals(map.get("code") + "")) {
                System.out.println("退出成功");
                tokenIdMap.remove(username);
                getTokenTimeMap.remove(username);
                digitalSignatureKeyMap.remove(username);
            } else {
                System.out.println("退出失败");
            }
        }
        return rs;
    }

    /**
     * 加密,登陆接口
     *
     * @param text
     * @return
     */
    private String encrypt(String text) {
        return encrypt(text, "civp");
    }

    /**
     * 加密
     *
     * @param rs
     * @param sys
     * @return
     */
    private String encrypt(String text, String sys) {
        if ("civp".equalsIgnoreCase(sys)) {
            text = Secret.encrypt(encryptionType, text, encryptionKey);
            return text;
        } else {
            text = Secret.encrypt(spiderEncryptionType, text, spiderEncryptionKey);
            return text;
        }
    }

    /**
     * 解密,登陆接口
     *
     * @param rs
     * @return
     */
    private String decrypt(String rs) {
        return decrypt(rs, "civp");
    }

    /**
     * 解密
     *
     * @param rs
     * @param sys
     * @return
     */
    private String decrypt(String rs, String sys) {
        if ("civp".equalsIgnoreCase(sys)) {
            rs = Secret.decrypt(encryptionType, rs, encryptionKey);
            return rs;
        } else {
            rs = Secret.decrypt(spiderEncryptionType, rs, spiderEncryptionKey);
            return rs;
        }
    }

    /**
     * 判定token是否还有效
     *
     * @param
     * @return
     */
    public boolean getNewToken() {
        Long tokenTime = getTokenTimeMap.get(username);
        long getTokenTime = tokenTime == null ? 0L : tokenTime;
        return tokenIdMap.get(username) == null || "".equals(tokenIdMap.get(username)) || System.currentTimeMillis() - getTokenTime >= tokenCycle;
    }

    public String getSpiderEncryptionType() {
        return spiderEncryptionType;
    }

    public void setSpiderEncryptionType(String spiderEncryptionType) {
        this.spiderEncryptionType = spiderEncryptionType;
    }

    public String getSpiderEncryptionKey() {
        return spiderEncryptionKey;
    }

    public void setSpiderEncryptionKey(String spiderEncryptionKey) {
        this.spiderEncryptionKey = spiderEncryptionKey;
    }

    public String rpad(String str, int strLength, char chr) {
        int strLen = str.length();
        if (strLen < strLength) {
            StringBuffer sb = new StringBuffer(str);
            while (strLen < strLength) {
                sb.append(chr); // 右补0
                strLen = sb.length();
            }
            str = sb.toString();
        } else {
            str = str.substring(0, strLength);
        }
        return str;
    }

    public String rpad(String str, int strLength) {
        return rpad(str, strLength, '0');
    }

    public String lpad(String str, int strLength, char chr) {
        int strLen = str.length();
        if (strLen < strLength) {
            while (strLen < strLength) {
                str = chr + str; // 左补0
                strLen = str.length();
            }
        }
        return str;
    }

    public String lpad(String str, int strLength) {
        return lpad(str, strLength, '0');
    }
}
